<?php
/*
 * description：
 * author：wh
 * email：
 * createTime：{2024/5/16} {18:02} 
 */

namespace wanghua\general_utility_tools_php\oss\alicloud;


use OSS\Core\OssException;
use OSS\Core\OssUtil;
use OSS\OssClient;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\tool\Tools;

/**
 * 文件对象操作类
 *
 * 包含上传、下载（可以上传字符串文本和文件）
 *
 * Class Objects
 * @package app\api\logic
 */
class Objects extends AliyunOSS
{

    /**
     * desc：文本上传
     * author：wh
     * @param string $object 保存文件名（包含完整路径）eg:exampledir/exampleobject.txt，Object完整路径中不能包含Bucket名称
     * @param string $content 上传文本内容
     * @param string $opts 上传时可以设置相关的headers，例如设置访问权限为private、自定义元数据等。
     *
     * 访问权限:
     * 文件的访问权限（ACL）有以下四种：default，private，public-read，public-read-write
     * 权限doc: https://help.aliyun.com/zh/oss/developer-reference/manage-object-acls-6?spm=a2c4g.11186623.0.0.63286dd53fYcTB
     * @return array
     */
    function txtUpload($object,$content='',$opts=[]){
        return Mmodel::catch(function () use ($object,$content,$opts){
            $options = [
                OssClient::OSS_HEADERS => $opts?:[
                    'x-oss-object-acl' => 'public-read',//$permissions='public-read-write'
                    'x-oss-meta-info' => $content
                ],
            ];
            return $this->ossClient->putObject($this->bucket, $this->file_save_path.$object, $content, $options);
        });
    }


    /**
     * desc：文件上传
     *
     * author：wh
     * @param string $object 上传后的文件名,完整路径中不能包含Bucket名称 如："exampledir/exampleobject.txt";
     * @param string $filePath 本地文件的完整路径，例如：D:\\localpath\\examplefile.txt, $file->getInfo('tmp_name')可获取临时文件路径
     * @param $opts 权限等其它header参数配置
     * 访问权限:
     * 文件的访问权限（ACL）有以下四种：default，private，public-read，public-read-write
     * 权限doc: https://help.aliyun.com/zh/oss/developer-reference/manage-object-acls-6?spm=a2c4g.11186623.0.0.63286dd53fYcTB
     * @return array
     */
    function fileUpload($object, $filePath, $opts=[]){
        return Mmodel::catch(function () use ($object, $filePath,$opts){
            $options = [
                OssClient::OSS_HEADERS => $opts?:[
                    'x-oss-object-acl' => 'public-read',//'public-read-write'
                ],
            ];
            return $this->ossClient->uploadFile($this->bucket, $object, $filePath,$options);
        });
    }

    /**
     * desc：文件流上传
     *
     * author：wh
     * @param $object
     * @param $stream
     * @param array $opts
     * @return array
     */
    function uploadFileStream($object, $stream, $opts=[]){
        return Mmodel::catch(function () use ($object, $stream,$opts){
            $options = [
                OssClient::OSS_HEADERS => $opts?:[
                    'x-oss-object-acl' => 'public-read',//'public-read-write'
                ],
            ];
            // 从文件流上传
            $res = $this->ossClient->putObject($this->bucket, $this->file_save_path.$object, $stream, $options);
            return $res;
        });
    }
    /**
     * desc：分片上传文件
     * author：wh
     * @param $object 上传后的文件名,完整路径中不能包含Bucket名称 如："exampledir/exampleobject.txt";
     * @param $uploadFile 填写本地文件的完整路径 如：'D:\\localpath\\examplefile.txt'
     */
    function multipartUpload($object,$uploadFile){
        $object = $this->file_save_path.$object;
        //填写不包含Bucket名称在内的Object完整路径，例如exampledir/exampleobject.txt。
        //$object = 'exampledir/exampleobject.txt';
        // 填写本地文件的完整路径。
        //$uploadFile = 'D:\\localpath\\examplefile.txt';
        $uploadId = '';
        $initOptions = array(
            OssClient::OSS_HEADERS  => array(
                // 指定该Object被下载时的网页缓存行为。
                // 'Cache-Control' => 'no-cache',
                // 指定该Object被下载时的名称。
                // 'Content-Disposition' => 'attachment;filename=oss_download.jpg',
                // 指定该Object被下载时的内容编码格式。
                // 'Content-Encoding' => 'utf-8',
                // 指定过期时间，单位为毫秒。
                'Expires' => 150,
                // 指定初始化分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
                'x-oss-forbid-overwrite' => 'false',
                // 指定上传该Object的每个part时使用的服务器端加密方式。
                // 'x-oss-server-side-encryption'=> 'KMS',
                // 指定Object的加密算法。
                // 'x-oss-server-side-data-encryption'=>'SM4',
                // 指定KMS托管的用户主密钥。
                //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****',
                // 指定Object的存储类型。
                'x-oss-storage-class' => 'Standard',
                // 指定Object的对象标签，可同时设置多个标签。
                // 'x-oss-tagging' => 'TagA=A&TagB=B',
            ),
            OssClient::OSS_ACL => OssClient::OSS_ACL_TYPE_PUBLIC_READ,
        );

        /**
         *  步骤1：初始化一个分片上传事件，并获取uploadId。
         */
        try{

            //返回uploadId。uploadId是分片上传事件的唯一标识，您可以根据uploadId发起相关的操作，如取消分片上传、查询分片上传等。
            $uploadId = $this->ossClient->initiateMultipartUpload($this->bucket, $object, $initOptions);
            //print("initiateMultipartUpload OK" . "\n");
            // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
            // 如果您需要根据您需要uploadId执行取消分片上传事件的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。
            // 如果您需要根据您需要uploadId执行列举已上传分片的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后，且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
            //print("UploadId: " . $uploadId . "\n");
        } catch(OssException $e) {
            Tools::error_txt_log($e);
            return Tools::set_fail('初始化一个分片上传事件，并获取uploadId ERROR.'.$e->getMessage());
        }
        //dump('$uploadId:'.$uploadId);

        /*
         * 步骤2：上传分片。
         */
        $partSize = 10 * 1024 * 1024;
        $uploadFileSize = sprintf('%u',filesize($uploadFile));
        $pieces = $this->ossClient->generateMultiuploadParts($uploadFileSize, $partSize);
        $responseUploadPart = array();
        $uploadPosition = 0;
        $isCheckMd5 = true;
        foreach ($pieces as $i => $piece) {
            $fromPos = $uploadPosition + (integer)$piece[$this->ossClient::OSS_SEEK_TO];
            $toPos = (integer)$piece[$this->ossClient::OSS_LENGTH] + $fromPos - 1;
            $upOptions = array(
                // 上传文件。
                $this->ossClient::OSS_FILE_UPLOAD => $uploadFile,
                // 设置分片号。
                $this->ossClient::OSS_PART_NUM => ($i + 1),
                // 指定分片上传起始位置。
                $this->ossClient::OSS_SEEK_TO => $fromPos,
                // 指定文件长度。
                $this->ossClient::OSS_LENGTH => $toPos - $fromPos + 1,
                // 是否开启MD5校验，true为开启。
                $this->ossClient::OSS_CHECK_MD5 => $isCheckMd5,
            );
            // 开启MD5校验。
            if ($isCheckMd5) {
                $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
                $upOptions[$this->ossClient::OSS_CONTENT_MD5] = $contentMd5;
            }
            try {
                // 上传分片。
                $responseUploadPart[] = $this->ossClient->uploadPart($this->bucket, $object, $uploadId, $upOptions);
                //printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n");
            } catch(OssException $e) {
                Tools::error_txt_log($e);
                //printf("initiateMultipartUpload, uploadPart - part#{$i} FAILED\n");
                //printf($e->getMessage() . "\n");
                return Tools::set_fail('上传分片ERROR.'.$e->getMessage());
            }
        }
        // $uploadParts是由每个分片的ETag和分片号（PartNumber）组成的数组。
        $uploadParts = array();
        foreach ($responseUploadPart as $i => $eTag) {
            $uploadParts[] = array(
                'PartNumber' => ($i + 1),
                'ETag' => $eTag,
            );
        }
        //dump($uploadParts);
        //die;
        /**
         * 步骤3：完成上传。
         */
        $comOptions['headers'] = array(
            // 指定完成分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
            'x-oss-forbid-overwrite' => 'false',
            // 如果指定了x-oss-complete-all:yes，则OSS会列举当前uploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
            // 'x-oss-complete-all'=> 'yes'
        );

        try {
            // 执行completeMultipartUpload操作时，需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            $res = $this->ossClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts,$comOptions);
            //dump($res);
            //printf( "Complete Multipart Upload OK\n");
            $this->ossClient->putObjectAcl($this->bucket, $object, OssClient::OSS_ACL_TYPE_PUBLIC_READ);
            return Tools::xml_to_array(($res['body']));
        }  catch(OssException $e) {
            Tools::error_txt_log($e);
            //printf("Complete Multipart Upload FAILED\n");
            //printf($e->getMessage() . "\n");
            return Tools::set_fail('ERROR:Complete Multipart Upload FAILED.'.$e->getMessage());
        }
    }

    /**
     * desc：查询文件列表
     * author：wh
     * @param array $options
     * @return array
     */
    function getLists($options=[]){
        return Mmodel::catch(function () use ($options){
            $options = $options?:[
                'max-keys'=>200,//设置最大个数为200。
                'delimiter' => '',//分隔符，表示将Object名称按该分隔符进行截断，并返回结果。
            ];
            $listObjectInfo = $this->ossClient->listObjects($this->bucket,$options);
            //文件列表
            $objectList = $listObjectInfo->getObjectList();
            //前缀列表
            //$prefixList = $listObjectInfo->getPrefixList();
            $arr = [];
            foreach ($objectList as $objectInfo) {
                $arr[] = $objectInfo->getKey();
            }
            return $arr;
        });
    }

    /**
     * desc：文件下载到本地
     *
     * author：wh
     * @param string $object 存储空间内的文件名 eg: testfolder/exampleobject.txt
     *
     * @param string $save_local_path 本地保存路径 eg: D:\\localpath\\examplefile.txt
     * 如果指定的本地文件存在会覆盖，不存在则新建。如果未指定本地路径，则下载后的文件默认保存到示例程序所属项目对应本地路径中。
     *
     * @param array $options 可选参数
     */
    function downloadLocal($object,$save_local_path='',$options=[]){
        // 填写不包含Bucket名称在内的Object完整路径，例如testfolder/exampleobject.txt。
        //$object = "testfolder/exampleobject.txt";
        // 下载Object到本地文件examplefile.txt，并保存到指定的本地路径中（D:\\localpath）。如果指定的本地文件存在会覆盖，不存在则新建。
        // 如果未指定本地路径，则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        //$save_local_path = "D:\\localpath\\examplefile.txt";
        $opts = [];
        if($save_local_path){
            $opts = array(
                OssClient::OSS_FILE_DOWNLOAD => $save_local_path
            );
        }

        if($options){
            $opts = array_merge($opts, $options);
        }
        // 使用try catch捕获异常。如果捕获到异常，则说明下载失败；如果没有捕获到异常，则说明下载成功。
        try{
            $res = $this->ossClient->getObject($this->bucket, $object, $opts);
            if($res){
                return $res;
            }
            //下载成功
            //print(__FUNCTION__ . ": OK, please check localfile: 'examplefile.txt'" . "\n");
            return Tools::set_ok('ok',['save_path'=>$save_local_path]);
        } catch(OssException $e) {
            Tools::error_txt_log($e);
            //printf(__FUNCTION__ . ": FAILED\n");
            //printf($e->getMessage() . "\n");
            return Tools::set_fail('ERROR:'.$e->getMessage());
        }
    }

    /**
     * desc：下载文件到内存
     *
     * author：wh
     * @param $object
     * @throws \OSS\Http\RequestCore_Exception
     */
    function downloadMemory($object){
        try{
            $this->ossClient->getObject($this->bucket, $object);
            //dump($content);
            return Tools::set_ok('下载文件到内存ok');
        } catch(OssException $e) {
            Tools::error_txt_log($e);
            return Tools::set_fail('ERROR:'.$e->getMessage());
        }
    }


}